/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.farent.solutions.common.discovery.ejbs;

import com.farent.solutions.common.discovery.FieldVisitor;
import com.farent.solutions.common.discovery.HasAnnotationSelectionStrategy;
import com.farent.solutions.common.discovery.IsAssignableFromSelectionStrategy;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ejb.EJB;
import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateful;
import javax.ejb.Stateless;

/**
 *
 * @author Farouk.Alhassan
 */
public class EJBFieldVisitor implements FieldVisitor<EJB> {

    private LinkedHashMap<Class, Object> register = new LinkedHashMap<Class, Object>();

    private List<Class> statelessClasses;

    private List<Class> statefullClasses;

    private Map<Class<? extends Annotation>, EjbAnnotationProvider> providers =
            new LinkedHashMap<Class<? extends Annotation>, EjbAnnotationProvider>();

    @Override
    public <V> V visit(V v) throws Exception {
        Field[] fields = v.getClass().getDeclaredFields();
        setFields(v, fields);
        Class<? extends Object> superclass = v.getClass().getSuperclass();
        traverseParent(v, superclass);
        return v;
    }

    private <V> void traverseParent(V v, Class<? extends Object> superclass) throws Exception {
        Field[] fields = superclass.getDeclaredFields();
        setFields(v, fields);

        if (superclass.getSuperclass() != null) {
            traverseParent(v, superclass.getSuperclass());
        }
    }

    private <V> void setFields(V v, Field[] fields) throws Exception {
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            if (field.isAnnotationPresent(EJB.class)) {
                Class<?> ejb = field.getType();
                Object ejbInstance = register.get(ejb);
                field.setAccessible(true);
                field.set(v, ejbInstance);
                visit(ejbInstance);
            } else {
                Set<Class<? extends Annotation>> keys = providers.keySet();
                for (Iterator<Class<? extends Annotation>> it = keys.iterator(); it.hasNext();) {
                    Class<? extends Annotation> anno = it.next();
                    if (field.isAnnotationPresent(anno)) {
                        EjbAnnotationProvider provider = providers.get(anno);
                        Object provided = provider.provide();
                        field.setAccessible(true);
                        field.set(v, provided);
                    }
                }
            }
        }
    }

    public void scanAndInitializeEjbs() throws Exception {
        HasAnnotationSelectionStrategy annotationStrategy = new HasAnnotationSelectionStrategy();

        EJBClassResolverProcessor annotaionsProcessor = new EJBClassResolverProcessor(annotationStrategy);

        IsAssignableFromSelectionStrategy assignableStrategy = new IsAssignableFromSelectionStrategy();

        EJBClassResolverProcessor instance = new EJBClassResolverProcessor(assignableStrategy);

        annotaionsProcessor.initialize();
        if (statelessClasses == null || statelessClasses.isEmpty()) {
            statelessClasses = annotaionsProcessor.getClassesWithAnnotation(Stateless.class);
            for (Class clazz : statelessClasses) {
                if (Modifier.isInterface(clazz.getModifiers())) {
                    List<Class> classsesImplementing = instance.getClasssesImplementing(clazz);
                    Class implementingClass = classsesImplementing.get(0);
                    Object newInstance = implementingClass.newInstance();
                    register.put(clazz, newInstance);
                } else if (!Modifier.isAbstract(clazz.getModifiers())) {
                    Object newInstance = clazz.newInstance();
                    register.put(clazz, newInstance);
                    if (clazz.getInterfaces().length != 0) {
                        Class[] interfaces = clazz.getInterfaces();
                        for (int i = 0; i < interfaces.length; i++) {
                            Class interfaze = interfaces[i];
                            if (interfaze.isAnnotationPresent(Local.class)
                                    || interfaze.isAnnotationPresent(Remote.class)) {
                                register.put(interfaze, newInstance);
                            }
                        }
                    }
                }
            }
        }
        if (statefullClasses == null || statefullClasses.isEmpty()) {
            statefullClasses = annotaionsProcessor.getClassesWithAnnotation(Stateful.class);
        }
    }

    public LinkedHashMap<Class, Object> getRegister() {
        return register;
    }

    public Map<Class<? extends Annotation>, EjbAnnotationProvider> getProviders() {
        return providers;
    }
}
